#ifndef     __CMD_UART_H
#define     __CMD_UART_H

#include "../conn/message.h"
#include "../conn/proto.h"
#include "../rtos/event_flags.h"
#include "../rtos/message_queue.h"
#include "../utils/byte_view.h"
#include <stdarg.h>
#include <stdio.h>


namespace driver {

class CmdUart : private utils::NoCopyable {

public:
    static constexpr uint32_t RECV_BUF_SIZE = 320;
    static constexpr uint32_t WRITE_BUF_SIZE = 96;

    static constexpr uint32_t FLAG_DMA_TC = 0x01;
    static constexpr uint32_t FLAG_RES_LOCK = 0x02;
    static constexpr uint32_t FLAG_ACK = 0x04;
    
    void init();

    void printf(const char *format, va_list ap);

    osStatus_t poll(utils::ByteView &bw, uint32_t timeout = osWaitForever) {
        return m_mq.poll(bw, timeout);
    }

    void on_ack() { m_flags.set_flags(FLAG_ACK); }

    void pong(uint8_t seq) { make_and_write(conn::proto::TYPE_PONG, seq); }

    void ack(uint8_t seq) { make_and_write(conn::proto::TYPE_ACK, seq); }

    template <typename... Ts>
    void simple_res(const conn::Message &req, const utils::Error &err,
                    const Ts &...ts) {
        make_and_write(conn::proto::TYPE_SIMPLE_RES, req.seq(), req.cmd(),
                       err.err_code, ts...);
    }

    template <typename... Ts>
    void notify(uint8_t seq, uint8_t cmd, const Ts &...ts) {
        make_and_write(conn::proto::TYPE_NOTIFY, seq, cmd, ts...);
    }

    template <typename... Ts>
    void res(const conn::Message &req, const utils::Error &err,
             const Ts &...ts) {
        m_flags.wait_any_flags(FLAG_RES_LOCK);
        m_flags.clr_flags(FLAG_ACK);
        for (uint8_t i = 0; i < 3; i++) {
            make_and_write(conn::proto::TYPE_RES, req.seq(), req.cmd(),
                           err.err_code, ts...);
            if ((m_flags.wait_any_flags(FLAG_ACK, 500) & osFlagsError) ==
                0x00) {
                break;
            }
        }
        m_flags.set_flags(FLAG_RES_LOCK);
    }

    template <typename... Ts>
    void notify_ack(const conn::Message &req, const Ts &...ts) {
        m_flags.wait_any_flags(FLAG_RES_LOCK);
        m_flags.clr_flags(FLAG_ACK);
        for (uint8_t i = 0; i < 3; i++) {
            make_and_write(conn::proto::TYPE_NOTIFY_ACK, req.seq(), req.cmd(),
                           ts...);
            if ((m_flags.wait_any_flags(FLAG_ACK, 500) & osFlagsError) ==
                0x00) {
                break;
            }
        }
        m_flags.set_flags(FLAG_RES_LOCK);
    }

    void on_recv_idle();
    void on_dma_tc() { m_flags.set_flags(FLAG_DMA_TC); }

private:
    template <typename... Ts>
    void make_and_write(uint8_t type, uint8_t seq, const Ts &...ts) {
        m_flags.wait_any_flags(FLAG_DMA_TC);
        uint8_t len = conn::proto::make(m_write_buf, type, seq, ts...);
        write_by_dma(m_write_buf, len);
    }

    void write_by_dma(const void *buf, uint16_t len);

    uint8_t m_recv_buf[RECV_BUF_SIZE];
    uint8_t m_write_buf[WRITE_BUF_SIZE];
    volatile uint32_t m_recv_index = 0;
    rtos::MessageQueue<utils::ByteView, 5> m_mq;
    rtos::EventFlags m_flags;
};

extern CmdUart cmdUart;

} // namespace driver

#endif
